Skip to content

Add logging capabilities to AIProjectClient and related samples - #48394

Merged
howieleung merged 9 commits into
mainfrom
howie/log
Aug 4, 2026
Merged

Add logging capabilities to AIProjectClient and related samples#48394
howieleung merged 9 commits into
mainfrom
howie/log

Conversation

@howieleung

Copy link
Copy Markdown
Member
  • Introduced a new logging transport class (_OpenAILoggingTransport) for handling OpenAI requests and responses.
  • Enhanced AIProjectClient to support console logging and custom user agents.
  • Created utility functions for generating timestamped log files.
  • Added multiple sample scripts demonstrating logging configurations, including:
    • Capturing both Azure-core and OpenAI transport logs.
    • Writing logs to console and files with different logging levels.
  • Implemented unit tests for logging behavior in both synchronous and asynchronous contexts.
  • Updated test helpers to accommodate new logging features and configurations.

Description

Please add an informative description that covers that changes made by the pull request and link all relevant issues.

If an SDK is being regenerated based on a new API spec, a link to the pull request containing these API spec changes should be included above.

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

- Introduced a new logging transport class (_OpenAILoggingTransport) for handling OpenAI requests and responses.
- Enhanced AIProjectClient to support console logging and custom user agents.
- Created utility functions for generating timestamped log files.
- Added multiple sample scripts demonstrating logging configurations, including:
  - Capturing both Azure-core and OpenAI transport logs.
  - Writing logs to console and files with different logging levels.
- Implemented unit tests for logging behavior in both synchronous and asynchronous contexts.
- Updated test helpers to accommodate new logging features and configurations.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
9 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds configurable logging for synchronous and asynchronous OpenAI clients created through AIProjectClient.

Changes:

  • Adds dedicated HTTPX logging transports with configurable redaction.
  • Adds console/file logging samples and utilities.
  • Adds synchronous and asynchronous logging tests.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/responses/test_openai_client_overrides.py Updates sync transport tests.
tests/responses/test_openai_client_overrides_async.py Updates async transport tests.
tests/responses/test_client_logging.py Tests sync logging behavior.
tests/responses/test_client_logging_async.py Tests async logging behavior.
tests/responses/openai_test_helpers.py Adds logging configuration to test clients.
samples/logs/util.py Exposes shared sample helpers.
samples/logs/log_utils.py Creates timestamped log paths.
samples/logs/sample_log_with_logging_disabled.py Demonstrates reduced logging.
samples/logs/sample_log_to_console.py Demonstrates console logging.
samples/logs/sample_log_from_sdk.py Demonstrates Azure SDK logging.
samples/logs/sample_log_from_openai_client.py Demonstrates OpenAI transport logging.
samples/logs/sample_log_all.py Demonstrates combined logging.
azure/ai/projects/_patch.py Implements synchronous logging transport and wiring.
azure/ai/projects/_patch.pyi Adds synchronous logging type declarations.
azure/ai/projects/aio/_patch.py Implements asynchronous logging transport.
azure/ai/projects/aio/_patch.pyi Adds asynchronous logging type declarations.

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py Outdated
Copilot AI review requested due to automatic review settings August 1, 2026 03:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Suppressed comments (3)

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:46

  • This explicit logging_enable=False prevents NetworkTraceLoggingPolicy from emitting Azure-core HTTP traces, while the console-logging constructor sets azure.core.pipeline.policies.http_logging_policy to ERROR. Consequently, this sample advertised as capturing both Azure-core and OpenAI HTTP logs only emits the OpenAI transport logs. Either enable network tracing here or preserve the redacted HTTP policy when full logging is disabled.
    AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=False) as project_client,

sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59

  • The sample test suite discovers files only within folders explicitly passed to get_sample_paths, and tests/samples/test_samples.py has no registration for logs. As a result, none of the five new logging samples run in CI. Add a recorded sample test for get_sample_paths("logs", ...), explicitly skipping by filename only where recording is not possible.
with (
    DefaultAzureCredential() as credential,
    AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,

sdk/ai/azure-ai-projects/.tmp_probe_openai_stream.py:1

  • This is a one-off diagnostic probe that executes immediately on import, prints request details, and deliberately raises an exception; it is neither a package module nor a test/sample covered by the PR. Remove this temporary file before merging.
import httpx

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py
Copilot AI review requested due to automatic review settings August 1, 2026 04:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Suppressed comments (4)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:364

  • When reduced logging is selected, this still writes the complete URL (including caller-supplied default_query values), and _sanitize_auth_header leaves an api-key header unchanged before this loop emits it. This contradicts the documented default redaction and can expose credentials or sensitive query parameters in ordinary debug logs. Redact query values and every credential-bearing header unless explicit body logging is enabled; authentication credentials should remain redacted in either mode.
        _OPENAI_TRANSPORT_LOGGER.debug("\n==> Request:\n%s %s", request.method, request.url)
        headers = dict(request.headers)
        self._sanitize_auth_header(headers)
        _OPENAI_TRANSPORT_LOGGER.debug("Headers:")
        for key, value in sorted(headers.items()):
            _OPENAI_TRANSPORT_LOGGER.debug("  %s: %s", key, value)

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:270

  • The async path also logs the full URL and emits api-key unchanged when logging_enabled=False. Because callers may supply arbitrary default_query values and headers, reduced logging can leak sensitive data. Redact URL query values and all credential-bearing headers in the async transport as well.
        _OPENAI_TRANSPORT_LOGGER.debug("\n==> Request:\n%s %s", request.method, request.url)
        headers = dict(request.headers)
        self._sanitize_auth_header(headers)
        _OPENAI_TRANSPORT_LOGGER.debug("Headers:")
        for key, value in sorted(headers.items()):
            _OPENAI_TRANSPORT_LOGGER.debug("  %s: %s", key, value)

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • This description says the console sample writes to a file, but the implementation only enables AZURE_AI_PROJECTS_CONSOLE_LOGGING and writes to the console. Update the description so users are not directed to expect a log file.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:26

  • This adds user-visible OpenAI transport logging behavior, but CHANGELOG.md still ends at 2.4.0 and contains no entry for the feature. Add a release-history entry describing the new logging behavior and samples so the significant SDK change is discoverable to users.
_OPENAI_TRANSPORT_LOGGER_NAME = "azure.ai.projects.openai_transport"
_OPENAI_TRANSPORT_LOGGER = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py
Comment thread sdk/ai/azure-ai-projects/samples/logs/log_utils.py Outdated
Copilot AI review requested due to automatic review settings August 1, 2026 04:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (4)

sdk/ai/azure-ai-projects/samples/logs/log_utils.py:11

  • This annotation is evaluated when the module is imported, but str | Path requires Python 3.10 while this package supports Python 3.9 (pyproject.toml:31). Running any of these samples on Python 3.9 will therefore fail while importing log_utils; use typing.Union for compatibility.
def create_timestamped_temp_log_file(script_path: str | Path) -> Path:

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:215

  • Passing a plain httpx.Client changes OpenAI's defaults for every generated client, including when logging is disabled. OpenAI 2.8 documents that custom httpx.Client instances use HTTPX defaults instead of its 600-second timeout, larger connection limits, and redirect handling; this can make existing long-running or redirected requests fail. Construct the logging client with openai.DefaultHttpxClient (or explicitly preserve all OpenAI defaults).
        return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:143

  • Passing a plain httpx.AsyncClient changes OpenAI's async defaults for every generated client, including when logging is disabled. OpenAI 2.8 documents that custom clients use HTTPX defaults instead of its 600-second timeout, larger connection limits, and redirect handling; this can make existing long-running or redirected requests fail. Construct the transport with openai.DefaultAsyncHttpxClient (or explicitly preserve all OpenAI defaults).
        return httpx.AsyncClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • The description says this console sample writes logs into a file, contradicting both its name and the later statement on line 25. Describe the console destination here so users do not expect a log file to be created.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

Copilot AI review requested due to automatic review settings August 1, 2026 05:00
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Suppressed comments (9)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:378

  • This only treats SSE as streaming. The returned OpenAI client also exposes with_streaming_response for non-SSE responses (for example file downloads with application/octet-stream), and those responses still take the response.read() branch and are fully buffered before the caller sees them. Preserve the response stream for every streaming request rather than inferring streaming solely from Content-Type.
        if self._is_streaming_response(response):
            _OPENAI_TRANSPORT_LOGGER.debug("Body: [Streaming response not logged]")
        else:
            content = response.read()

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:364

  • The sanitizer's contract includes api-key, but it only rewrites authorization. Because get_openai_client accepts caller-provided default_headers, an api-key header is logged verbatim here even with logging_enable=False. Redact api-key (case-insensitively) in reduced mode before iterating the headers.
        _OPENAI_TRANSPORT_LOGGER.debug("Headers:")
        for key, value in sorted(headers.items()):
            _OPENAI_TRANSPORT_LOGGER.debug("  %s: %s", key, value)

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:270

  • The async sanitizer says it handles api-key, but it only redacts authorization. A caller-supplied default_headers={"api-key": ...} value is therefore emitted unchanged here under reduced logging. Redact api-key case-insensitively whenever logging_enable=False.
        _OPENAI_TRANSPORT_LOGGER.debug("Headers:")
        for key, value in sorted(headers.items()):
            _OPENAI_TRANSPORT_LOGGER.debug("  %s: %s", key, value)

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:284

  • This only protects SSE streams. Async OpenAI's with_streaming_response can stream non-SSE payloads such as file downloads, which still reach response.aread() here and are fully buffered before being returned. Preserve the async response stream for all streaming requests instead of using only the response content type as the signal.
        if self._is_streaming_response(response):
            _OPENAI_TRANSPORT_LOGGER.debug("Body: [Streaming response not logged]")
        else:
            content = await response.aread()

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:215

  • Supplying a plain httpx.Client replaces OpenAI's own default HTTP client even when logging is disabled. In particular, httpx defaults follow_redirects to false while OpenAI's default client enables it, so redirects that previously succeeded can now be returned as errors; other OpenAI connection defaults are also bypassed. Build this around OpenAI's default client configuration (or reproduce all of its defaults) when installing the transport.
        logging_kwargs = getattr(self, "_kwargs", {})
        logging_enabled = bool(logging_kwargs.get("logging_enable", False))
        return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:143

  • This replaces OpenAI's default async HTTP client for every caller, including logging_enable=False. A plain httpx.AsyncClient does not preserve OpenAI's defaults (notably follow_redirects=True), so redirected requests can regress and other connection settings may change. Use OpenAI's default async client configuration while injecting this transport.
        logging_kwargs = getattr(self, "_kwargs", {})
        logging_enabled = bool(logging_kwargs.get("logging_enable", False))
        return httpx.AsyncClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))

sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59

  • None of the five new samples/logs/sample_*.py files is registered in tests/samples/test_samples.py. That suite discovers samples only within folders explicitly passed to get_sample_paths, and there is currently no logs entry, so these samples—including their local utility imports and logger setup—will never execute in CI. Add a logs sample parametrization, or explicitly skip individual filenames with a reason if they cannot be recorded.
with (
    DefaultAzureCredential() as credential,
    AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:323

  • The capture handler is attached both to the root logger and directly to this child logger, but the transport logger normally has propagate=True. Every OpenAI transport record is therefore appended to print_calls twice in ordinary samples, inflating and duplicating the text sent to LLM validation. Temporarily disable propagation while the direct handler is installed, then restore the prior value.
        directly_attached_loggers = []
        for logger_name in ("azure.ai.projects.openai_transport",):
            logger_instance = logging.getLogger(logger_name)
            logger_instance.addHandler(capture_handler)
            directly_attached_loggers.append(logger_instance)

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • The description says this console sample writes logs into a file, contradicting both its name and the usage note below. Describe the console destination so users do not look for a log file that is never created.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py Outdated
Copilot AI review requested due to automatic review settings August 3, 2026 21:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (4)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:393

  • The OpenAI client’s limits=DEFAULT_CONNECTION_LIMITS setting does not configure a caller-supplied transport—HTTPX returns that transport unchanged. Because this transport initializes HTTPTransport with its own defaults, all default OpenAI clients now use HTTPX’s smaller 100/20 connection pool instead of OpenAI’s 1000/100 pool, which can introduce connection-pool contention under concurrency. Initialize both custom transports with OpenAI’s default connection limits.
        super().__init__()

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:238

  • DefaultAsyncHttpxClient cannot apply its DEFAULT_CONNECTION_LIMITS to this supplied transport because HTTPX uses a custom transport unchanged. This super().__init__() therefore reduces the async OpenAI pool from OpenAI’s 1000/100 defaults to HTTPX’s 100/20 defaults, potentially throttling concurrent workloads. Initialize both custom transports with OpenAI’s default connection limits.
        super().__init__()

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:340

  • Removing the environment-variable check makes the shipped SAMPLE_TEST_ERROR_LOG, SAMPLE_TEST_FAILED_LOG, and SAMPLE_TEST_PASSED_LOG settings in `.env.template:101-107 dead configuration, although that file still says uncommenting them enables logging. Update/remove those settings and comments, or retain the gating, so users are not given ineffective configuration.
    def _build_live_log_file_path(self, suffix: str) -> Optional[str]:
        """Build a live-mode sample log path in the system temp directory."""

        # Only create logs in live mode
        if not _is_live_mode():
            return None

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • This description contradicts the sample: no file handler is created, and line 39 enables console logging, which causes the client to default logging_enable to True, not False. Describe console output and the console redaction behavior instead.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

Copilot AI review requested due to automatic review settings August 3, 2026 21:30
Comment thread sdk/ai/azure-ai-projects/README.md Outdated
Comment thread sdk/ai/azure-ai-projects/README.md Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Suppressed comments (7)

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456

  • The new “print output” log is not faithful to print(): capture discards sep and end, and this writer appends a newline to every call. For the added streaming samples, print(event.delta, end="") is therefore rewritten as one line per delta. Preserve each call's rendered separator/terminator (or capture into a text buffer) and write it verbatim; update the CLI executor overrides as well.
            if self.print_output_calls:
                for print_call in self.print_output_calls:
                    file_handle.write(f"{print_call}\n")

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233

  • Each client constructed with AZURE_AI_PROJECTS_CONSOLE_LOGGING=true creates and permanently attaches another handler to this process-global logger. Creating two clients therefore emits every OpenAI transport record twice, and closing either client does not restore the logger. Make this setup idempotent or manage the handler's lifetime explicitly; the async constructor must use the same shared strategy.
            openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
            openai_transport_logger.setLevel(logging.DEBUG)
            openai_transport_logger.propagate = False
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113

  • This async constructor also adds a fresh handler to the same global transport logger on every client creation. Mixing sync/async clients or constructing multiple async clients duplicates each log record and leaves handlers behind after clients close. Use the same idempotent, lifecycle-managed logger configuration as the sync path.
            openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
            openai_transport_logger.setLevel(logging.DEBUG)
            openai_transport_logger.propagate = False
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • This description contradicts the sample: it configures console output, not a file, and the environment flag causes logging_enable to default to True, not False. Describe the actual console-logging behavior so users do not expect reduced file logging.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12

  • The transport now wraps SSE streams and logs every raw response chunk when logging_enable=True, so the statement that streamed events are not written to SDK logs is incorrect. Clarify that raw stream chunks go to the log while parsed events are printed to the console.
    With logging_enable=True, request bodies, response metadata, and token are
    included in the log file. Streamed response events are printed to the
    console and are not automatically written to SDK logs.

sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59

  • None of the new samples/logs/sample_*.py scripts are collected: tests/samples/test_samples.py registers folders through explicit get_sample_paths(...) calls, and there is no logs entry. Add sync/async sample coverage for this folder, using exact-filename skips only for scripts that cannot run under recorded tests.
with (
    DefaultAzureCredential() as credential,
    AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:384

  • This adds a user-visible logging transport and changes the default get_openai_client() HTTP-client behavior, but the PR has no CHANGELOG entry. The package records user-visible features under CHANGELOG.md “Features Added” (for example lines 5–16); add an entry for this feature before release.
class _OpenAILoggingTransport(httpx.HTTPTransport):
    """Custom HTTP transport that logs OpenAI API requests and responses.

    This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and emit
    detailed request/response information through a dedicated logger. It automatically

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
Copilot AI review requested due to automatic review settings August 4, 2026 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (4)

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • This description contradicts the sample: it writes to the console, not a file, and setting AZURE_AI_PROJECTS_CONSOLE_LOGGING=true makes the client default logging_enable to True, so request and response bodies are included rather than excluded. Update the description to match the demonstrated console/full-logging behavior.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:9

  • None of the new samples/logs/sample_*.py scripts are exercised by the package's sample suite. get_sample_paths discovers samples only within folders explicitly parameterized in tests/samples/test_samples.py, and that file has no logs entry. Add sync/async sample coverage (or explicit skips with reasons) so these executable examples are validated like the other sample folders.
"""
DESCRIPTION:
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456

  • The new print-only log does not preserve actual print() semantics: _capture_print discards sep/end, and this loop then appends a newline after every call. For example, sample_log_stream_events*.py uses print(event.delta, end=""), but its output log will put every streamed delta on a separate line. Capture each call with its effective sep and end and write those fragments verbatim; the sync and async CLI overrides need the same treatment.
                for print_call in self.print_output_calls:
                    file_handle.write(f"{print_call}\n")

sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12

  • With full logging enabled, the new transport wrapper logs each SSE body chunk lazily as it is consumed, so streamed response data is automatically written to this log file. The sample description currently says the opposite.
    With logging_enable=True, request bodies, response metadata, and token are
    included in the log file. Streamed response events are printed to the
    console and are not automatically written to SDK logs.

@github-actions

This comment has been minimized.

Copilot AI review requested due to automatic review settings August 4, 2026 05:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233

  • This handler is added to a process-global logger for every client construction and is never removed by AIProjectClient.close(). Creating two console-logging clients causes every OpenAI transport record to be emitted twice, and the duplication continues after either client closes. Make this setup idempotent or retain and remove the client-owned handler during close.
            openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
            openai_transport_logger.setLevel(logging.DEBUG)
            openai_transport_logger.propagate = False
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:384

  • This adds user-visible logging behavior and configuration, but the package CHANGELOG.md has no corresponding entry. Add the feature under the next release so consumers can discover the new transport logger and console/file logging behavior.
class _OpenAILoggingTransport(httpx.HTTPTransport):
    """Custom HTTP transport that logs OpenAI API requests and responses.

    This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and emit
    detailed request/response information through a dedicated logger. It automatically

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113

  • The async constructor also appends a new handler to the global transport logger on every client creation without removing it in close(). Multiple async clients therefore multiply each log line and leave the handlers installed after their contexts exit. Make handler installation idempotent or clean up the client-owned handler on close.
            openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
            openai_transport_logger.setLevel(logging.DEBUG)
            openai_transport_logger.propagate = False
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59

  • None of the new samples/logs files are registered in test_samples.py or test_samples_async.py. Since get_sample_paths only discovers files inside explicitly requested folders, these samples are never imported or executed in CI. Add sync/async sample test entries for this folder, with recordings or explicit skips where required.
with (
    DefaultAzureCredential() as credential,
    AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
[Pilot] PR Pipeline Failure Analysis

A CI pipeline failed on this pull request. Here is an automated analysis of what went wrong and how to get the build green.

What failed

The azure-ai-projects test suite failed across multiple platforms (macOS 3.11, Ubuntu 24.04 3.13) in both the sdist and mindependency configurations. The failures are concentrated in two areas:

  1. TestResponsesInstrumentor telemetry tests (sync + async, streaming + non-streaming, code interpreter, file search, MCP, workflow, metrics) — these are the new tests added or touched by this PR covering _OpenAILoggingTransport and telemetry instrumentation.
  2. Broad sample + integration tests — nearly all test_samples, test_agents_crud, test_conversation_crud, test_files, test_responses, test_openai_client_* tests are also failing, suggesting the PR changes may have introduced an import error or a broken dependency that causes the entire azure-ai-projects test collection to fail.

The same set of failures repeats identically across platforms and both sdist/mindependency wheels, which strongly suggests a code/import issue rather than an environment-specific flake.

Recommended next steps

  • Run the failing tests locally to get the actual error message: pytest sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py -x — the root error (e.g. ImportError, AttributeError, assertion mismatch) will be visible in the first failure.
  • Check for import errors in the new _OpenAILoggingTransport module and any __init__.py changes — a single bad import can cause the entire test module to fail collection, which would explain the broad failure scope.
  • Verify test recordings if any playback recordings were not updated to match the new transport/client changes.
  • Check the CHANGELOG and ensure it reflects the new logging feature (required for the PR checklist).
  • See the CI troubleshooting guide: https://aka.ms/ci-fix
  • Push new commits to address the failures; this comment updates automatically on the next failing run.
Raw pipeline analysis (azsdk ci analyze)
Analyzing pipeline https://github.com/Azure/azure-sdk-for-python/pull/48394...
--------------------------------------------------------------------------------
Failed Tests (representative sample — failures repeat across macos311 sdist, macos311 mindependency, Ubuntu2404_313 sdist, and additional platforms)
--------------------------------------------------------------------------------
Telemetry / ResponsesInstrumentor tests (sync + async):
  test_sync_non_streaming_with_content_recording_events
  test_sync_non_streaming_with_content_recording_attributes
  test_sync_streaming_with_content_recording_events
  test_sync_function_tool_* (non-streaming, streaming, simple format)
  test_image_only_*, test_text_and_image_* (content on/off, binary on/off)
  test_workflow_agent_*, test_prompt_agent_with_responses_*
  test_async_* counterparts of all of the above
  TestResponsesInstrumentorCodeInterpreter (sync + async)
  TestResponsesInstrumentorFileSearch (sync + async)
  TestResponsesInstrumentorMCP (sync + async)
  TestResponsesInstrumentorMetrics
  TestResponsesInstrumentorWorkflow (sync + async)

Agent / sample / integration tests:
  TestAgentResponsesCrud.test_agent_responses_crud
  TestAgentCrud.test_agent_disable_enable, test_prompt_agent_endpoint_responses
  TestConversationCrud, TestConversationItemsCrud
  TestFiles, TestResponses, TestGetOpenaiClient (endpoint + overrides)
  test_samples.TestSamples (all tool/memory/agent/evaluation samples)
  test_samples_evaluations.TestSamplesEvaluations (all agentic evaluator samples)

Artifact sources: LLM Artifacts - macos311 - 1, LLM Artifacts - Ubuntu2404_313 - 1
(failures are identical in both sdist and mindependency configurations)

Copilot detected the failing pipeline and generated the analysis above. To have it attempt a fix automatically, reply with @copilot please fix the failing pipeline on this PR.

Generated by Pipeline Analysis - Next Steps · 37.5 AIC · ⌖ 8.94 AIC · ⊞ 6.6K ·

Copilot AI review requested due to automatic review settings August 4, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (6)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:26

  • _openai_transport_loggerr contains a typo and is used throughout this module. Rename it to _openai_transport_logger consistently so the sync and async implementations use the same clear name.
_openai_transport_loggerr = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233

  • Each console-enabled client appends a new handler to this process-global logger, and client shutdown never removes it. Constructing two clients therefore emits every OpenAI transport record twice; repeated short-lived clients (including the sample executor) keep increasing output and retain stale sys.stdout streams. Install/reuse one process-level handler or remove the client-owned handler during shutdown.
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12

  • This says streamed responses are not written to SDK logs, but the new full-logging transport wraps the SSE stream and logs every raw chunk as it is consumed (_LoggingSyncByteStream), as the new transport test also asserts. Clarify that raw chunks are logged while parsed events are printed.
    With logging_enable=True, request bodies, response metadata, and token are
    included in the log file. Streamed response events are printed to the
    console and are not automatically written to SDK logs.

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113

  • The async constructor also adds a fresh handler to the same global transport logger on every client creation without removing it. Multiple async clients—or a sync and async client in one process—will duplicate every transport log and retain old stdout streams. Reuse a single configured handler or remove client-owned handlers when the client closes.
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • The description contradicts the sample: it says logs go to a file with logging_enable=False, but the code sets AZURE_AI_PROJECTS_CONSOLE_LOGGING=true and writes to the console. Update the description so users understand the behavior being demonstrated.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456

  • This does not preserve actual print() output: _capture_print discards sep/end, and this loop then adds a newline after every call. For example, the new streaming sample's print(event.delta, end="") will be written one chunk per line instead of as the displayed text. Capture the rendered fragments with their sep and end values and write them verbatim; update the CLI executor overrides similarly.
            if self.print_output_calls:
                for print_call in self.print_output_calls:
                    file_handle.write(f"{print_call}\n")

Copilot AI review requested due to automatic review settings August 4, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (13)

sdk/ai/azure-ai-projects/tests/samples/llm-analyze.py:170

  • The async CLI override drops sep and end too, causing its new print_output_file to insert line breaks between streamed deltas that were printed with end="". Preserve the print formatting and have the shared writer emit it verbatim.
    def _capture_print(self, *args, **_kwargs):
        text = " ".join(str(arg) for arg in args)
        self.print_calls.append(text)
        self.print_output_calls.append(text)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:149

  • This async finally message also labels failed or partially consumed streams as completed. Emit completion only when async iteration exhausts normally, and log/re-raise iteration failures separately so the new transport logs remain accurate.
        finally:
            _openai_transport_logger.debug("Body: [Streaming response completed]")

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:325

  • This handler is attached both here and to the root logger. When openai_transport has its default propagate=True, each record reaches the same handler twice, so print_calls and the generated reports contain duplicate OpenAI request/response entries. Temporarily disable propagation while the direct handler is installed, then restore it during cleanup.
            logger_instance.addHandler(capture_handler)
            directly_attached_loggers.append(logger_instance)

sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:239

  • print_output_calls is described and persisted as the captured print output, but this normalization ignores sep and end. Samples such as samples/responses/sample_responses_stream_events.py:57 use end="", so the output log writes every streamed delta on a separate line instead of reproducing the console output. Preserve each call's actual sep/end when building the output-only log.
        text = " ".join(str(arg) for arg in args)
        self.print_calls.append(text)
        self.print_output_calls.append(text)

sdk/ai/azure-ai-projects/tests/samples/llm-analyze.py:92

  • This override also drops sep and end, so the CLI's new print_output_file does not contain the actual printed output for streaming samples that use end=""; each token is later written on its own line. Preserve print formatting here as well as in the output-log writer.

This issue also appears on line 167 of the same file.

    def _capture_print(self, *args, **_kwargs):
        text = " ".join(str(arg) for arg in args)
        self.print_calls.append(text)
        self.print_output_calls.append(text)

sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11

  • The description contradicts the sample: it writes to the console, and AZURE_AI_PROJECTS_CONSOLE_LOGGING=true makes the constructor set logging_enable=True, not false. Update this text so users are not told that bodies are omitted when this sample enables detailed logging.
    This sample demonstrates how to capture both Azure-core HTTP logs and
    OpenAI transport logs into a single file while running a Prompt Agent operation.
    With logging_enable=False, the transport still logs request and response metadata,
    but excludes request bodies and response bodies while keeping sensitive headers redacted.

sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12

  • The transport now wraps SSE streams and logs every raw body chunk as it is consumed (_LoggingSyncByteStream), so the statement that streamed content is not written to SDK logs is incorrect. Clarify that parsed events go to the console while raw stream chunks are also written to this log file.
    With logging_enable=True, request bodies, response metadata, and token are
    included in the log file. Streamed response events are printed to the
    console and are not automatically written to SDK logs.

sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events_async.py:12

  • The async logging transport wraps SSE responses with _LoggingAsyncByteStream, which writes every consumed raw chunk to the SDK log. This description currently promises the opposite; distinguish the logged raw stream chunks from the parsed events printed to the console.
    operation. With logging_enable=True, request bodies, response metadata, and
    token are included in the log file. Streamed response events are printed to
    the console and are not automatically written to SDK logs as parsed events.

sdk/ai/azure-ai-projects/tests/responses/test_client_logging_async.py:160

  • This test attaches to the module logger, whereas the sync implementation and the README direct users to azure.ai.projects.openai_transport. The async implementation still emits its client-creation message through the module logger, so an async user following the documented dedicated-logger setup silently misses that event. Emit the async creation message through _openai_transport_logger and update this test to attach to the documented logger.
    handler = _attach_file_handler("azure.ai.projects.aio._patch", log_file)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233

  • Each client construction adds a new handler to this process-global logger, and closing the client never removes it. Creating two console-enabled clients therefore duplicates every OpenAI transport log (and retains both clients' handlers indefinitely). Configure this logger idempotently or track and remove the handler during client shutdown.
            openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
            openai_transport_logger.setLevel(logging.DEBUG)
            openai_transport_logger.propagate = False
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113

  • The async constructor also appends a handler to the shared azure.ai.projects.openai_transport logger on every client creation without removing it. Multiple sync/async clients then produce duplicate records and retain stale handlers. Make setup idempotent or remove each client-owned handler on close.
            openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
            openai_transport_logger.setLevel(logging.DEBUG)
            openai_transport_logger.propagate = False
            openai_transport_logger.addHandler(console_handler)

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:271

  • This adds user-visible logging behavior to AIProjectClient, but CHANGELOG.md still ends at 2.4.0 and contains no entry for it. The contribution checklist requires significant features to be recorded; add a Features Added entry (and the new logging samples) for the upcoming release.
        logging_kwargs = getattr(self, "_kwargs", {})
        logging_enabled = bool(logging_kwargs.get("logging_enable", False))
        return DefaultHttpxClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))

sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:131

  • The finally block reports a completed stream even when the underlying iterator raises or the consumer stops early, which makes failure diagnostics falsely look successful. Log completion only after normal exhaustion; on an iteration error, log that the stream was interrupted and re-raise it.

This issue also appears on line 148 of the same file.

        finally:
            _openai_transport_logger.debug("Body: [Streaming response completed]")

@howieleung
howieleung merged commit ed86ce8 into main Aug 4, 2026
21 checks passed
@howieleung
howieleung deleted the howie/log branch August 4, 2026 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants